System Design_大型网站可扩展架构

构建可扩展的网站架构

扩展性

指对对现有系统影响下,系统功能可持续扩展或提升的能力。表现在系统基础设置稳定不需要经常变更,应用之间较少依赖和耦合,对需求变更可以敏捷响应。也就是系统架构设计层面的开闭原则(对扩展开放,对修改关闭),架构设计考虑未来功能扩展,当系统增加新功能时,不需要对现有系统的结构和代码进行修改。

伸缩性

指系统能够通过增加(减少)自身资源规模的方式增强(减少)自己计算处理事务的能力。如果这种增减是成比例的,就被称作线性伸缩性。在网站架构中,通常指利用集群的方式增加服务器数量、提高系统的整体事务吞吐能力。

低耦合系统更容易扩展和复用。软件架构师的关键能力在于能够将一个大系统分成N个低耦合的子模块,这些子模块包含横向的业务模块,也包含纵向的基础基础模块。

设计网站可扩展架构的核心思想是模块化,并在此基础上,降低模块间的耦合性,提高模块的复用性。前面提到使用分层和分割的方式进行架构伸缩,分层和分割也是模块化设计的重要手段,利用分层和分割的方式将软件分割为若干个低耦合的独立的组件模块,这些组件模块以消息传递及依赖调用的方式聚合成一个完整的系统。

在大型网站中,这些模块通过分布式部署的方式,独立的模块部署在独立的服务器(集群)上,从物理上分离模块之间的耦合关系,进一步降低耦合性,提高复用性。

模块分布式部署后具体聚合方式主要有分布式消息队列和分布式服务。

利用分布式消息队列降低系统耦合性

如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响最小,这样可以实现很高的可扩展性。

事件驱动架构(Event Driven Architecture)

指通过在低耦合的模块之间传输时间消息,以保持模块的松散耦合,并借助事件消息的通信完成模块间合作,典型的EDA架构就是操作系统中常见的生产者消费者模式。在大型网站架构中,具体实现手段有很多,最常用的是分布式消息队列。

System_design_c7_01

消息队列利用发布-订阅模式工作,消息发送者发布消息,一个或多个消息接收者订阅消息。发送者和接收者没有直接耦合,可以新增业务为发布者或者接收者而无需影响原有系统业务,从而实现网站业务的可扩展设计。

另外由于消息发送者不需要等待消息接收者处理数据就可以返回,系统具有更好的响应延迟,同时,在网站访问高峰,消息可以暂时存储在消息队列中等待消息接收者根据自身负载处理能力控制消息处理速度,减轻数据库等后端存储的负载压力。

分布式消息队列

队列是一种先进先出的数据结构,分布式消息队列可以看做这种数据结构的分布式部署到独立服务器上,应用程序可以通过远程访问接口使用分布式消息队列,进行消息存取操作,进而实现分布式的异步调用。

System_design_c7_02

消息生产者通过远程接口将消息推送给消息队列服务器随后返回,消息队列服务器根据消息订阅列表查找订阅该消息的消费者程序,将消息队列中的消息按照先进先出FIFO将消息通过远程通信端口发送给消费者程序。

目前开源的分布式消息队列产品有Apache ActiveMQ等,这些产品另外也对可用性、伸缩性、数据一致性、性能也做了很多改善。

在伸缩性方面,由于消息队列服务器上的数据可以看做是无状态的,伸缩性设计比较简单。将服务器加入分布式消息队列集群中,通知生产者服务器更改消息队列服务器列表即可。

在可用性方面,为了避免消费者进行处理缓慢,分布式消息队列服务器内存空间不足造成的问题,如果内存队列已满,会将消息写入磁盘。内存队列消息处理完后,将磁盘内容加载到内存队列继续处理。

分布式消息队列可以很复杂,比如说支持ESB,SOA;也可以很简单,如用MySQL也可以当做分布式消息队列。

利用分布式服务打造可复用的业务平台

使用分布式服务是降低系统耦合性的另一个重要手段。分布式消息队列是通过消息对象分解系统耦合性,不同子系统处理同一个消息。分布式服务则是通过接口分解系统耦合性,不同子系统通过相同的接口进行服务调用。

网站架构是一个不断发展和变复杂的过程,会逐渐聚合大量子应用和服务组件,这个巨无霸会给整个网站的开发、维护、部署都带来巨大的麻烦:

  • 编译部署困难
  • 代码分治管理困难
  • 数据库连接耗尽
  • 新增业务困难

System_design_c7_03

解决方案就是拆分,将模块独立部署,降低系统耦合性。拆分可以分为纵向拆分和横向拆分。

纵向拆分:将一个大应用拆分为多个小应用,如果新增业务较为独立,那么久直接将其设计部署为一个独立的Web应用系统。

横向拆分:将复用的业务拆分出来,独立部署为分布式服务,其他子服务和新增服务只需要调用这些分布式服务,不需要依赖具体的模块代码,即可快速搭建一个应用系统,而模块内部业务逻辑变化时,只要接口保持一致就不会影响业务程序和其他模块。

System_design_c7_04

纵向拆分相对较为简单,通过梳理业务,将较少相关的业务分离使其成为独立的Web应用。而对于横向拆分,不但需要识别可复用的业务,设计服务接口,规范服务依赖关系,还需要一个完善的分布式服务管理框架。

Web Service与企业级分布式服务

Web Service是企业应用系统开发领域的重要技术,用以整合异构系统及构建分布式系统。

System_design_c7_05

服务提供者通过WSDL(Web Services description Language, Web服务描述语言)向注册中心(Service Broker)描述自身提供的服务接口属性,注册中心使用UDDI(Universal Desription, Discovery, and Integration, 统一描述发现和集成)发布服务提供者提供的服务,服务请求者从注册中心检索到服务信息后,通过SOAP(Simple Object Access Protocol, 简单对象访问协议)和服务提供者通信,使用相关服务。

Web Service具有跨平台和成熟等优点,但是也有如下的缺点:

  • 臃肿的注册与发现机制
  • 抵消的XML序列化手段
  • 开销相对较高的HTTP远程通信
  • 负载的部署和维护手段

这些问题导致Web Service难以满足大型网站对系统高性能、高可用、易部署、易维护的要求。

大型网站分布式服务的需求与特点

对于大型网站,除了Web Service所提供的服务注册与发现,服务调用等标准功能,还需要分布式服务框架能够支持如下特性。

  • 负载均衡

对热门服务,比如登录服务等,访问量非常大,服务需要部署在一个集群上。分布式服务框架需要能够支持服务请求者使用可配置的负载均衡算法访问服务,使得服务提供者集群实现负载均衡。

  • 失效转移

可复用的服务通常会被多个应用调用,一旦该服务不可用,可能会影响很多应用的可用性。因此对于大型网站的分布式服务而言,即使是很少访问的简单服务,也需要集群部署,分布式服务框架需要支持服务提供者的失效转移机制。

  • 高效的远程通信
  • 对应用最少侵入
  • 版本管理
  • 实时监控

分布式服务框架设计

大型网站需要更简单更高效的分布式服务框架构建其SOA(Service Oriented Architecture, 面向服务的体系架构)。Facebook利用Thrift(一个开源的远程服务调用框架)管理其分布式服务,服务的注册、发现及调用都通过Thrift完成,但是对于一个大型完整可以使用的分布式框架服务,仅有Thrift还远远不够,但是Facebook没有开源其基于Thrift的分布式服务框架。

目前有较多成功实施案例的开源分布式框架是Alibaba的Dubbo。

System_design_c7_06

服务消费者程序通过服务接口使用服务,而服务接口通过代理加载具体服务,具体服务可以是本地的代码模块,也可以是远程的服务,因此对应用较少侵入:应用程序只需要调用服务接口,服务框架根据配置自动调用本地或远程实现。

服务框架客户端模块通过服务注册中心加载服务提供者列表(服务提供者启动后自动向服务注册中心注册自己可提供的服务接口列表),查找需要的服务接口,并根据配置的负载均衡策略将服务调用请求发送到某台服务提供者服务器。如果服务调用失败,客户端模块会自动从服务提供者列表专责一个可提供同样服务的另一台服务器重新请求服务,实现服务的自动失效转移,保证服务高可用。

另外Dubbo的远程服务通信模块支持多种通信协议和数据序列化协议,使用NIO通信框架,具有较高网络通信性能。

可扩展的数据结构

传统的关系数据库需要预先定义表的schema,这些规范带来的一个问题就是僵硬的数据结构难以面对需求变更带来的挑战,也就是难以扩展。

使用NoSQL数据库的ColumnFamily(列族)设计是一个可行的解决方案,ColumnFamily最早在Google的Bigtable中使用,是一种面向列族的稀疏矩阵存储格式。

System_design_c7_07

使用支持ColumnFamily结构的NoSQL数据库,创建表时,只需要制定ColumnFamily的名字,无需指定字段(Column),可以在数据写入时再指定,通过这种方式,数据表可以包含数百万的字段,且可以随时扩展。而在查询时,同样可以通过指定任意字段名称和值进行查询。